﻿#define _CRT_SECURE_NO_WARNINGS 1

//4. 拷⻉构造函数 
//如果⼀个构造函数的第⼀个参数是⾃⾝类类型的引⽤，且任何额外的参数都有默认值，则此构造函数
//也叫做拷⻉构造函数，也就是说拷⻉构造是⼀个特殊的构造函数。


//拷⻉构造的特点：
//1. 拷⻉构造函数是构造函数的⼀个重载。

//2. 拷⻉构造函数的参数第一个参数必须是自身类类型对象的引⽤，使⽤传值⽅式编译器直接报错，因为
//语法逻辑上会引发⽆穷递归调⽤。
 
//3. C++规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造，所以这⾥⾃定义类型传值传参和传值返
//回都会调⽤拷⻉构造完成。

//4. 若未显式定义拷⻉构造，编译器会⽣成⾃动⽣成拷⻉构造函数。⾃动⽣成的拷⻉构造对内置类型成
//员变量会完成值拷⻉ / 浅拷⻉(⼀个字节⼀个字节的拷⻉)，对⾃定义类型成员变量会调⽤他的拷⻉构
//造。

//5. 像Date这样的类成员变量全是内置类型且没有指向什么资源，编译器⾃动⽣成的拷⻉构造就可以完
//成需要的拷⻉，所以不需要我们显⽰实现拷⻉构造。像Stack这样的类，虽然也都是内置类型，但
//是_a指向了资源，编译器⾃动⽣成的拷⻉构造完成的值拷⻉ / 浅拷⻉不符合我们的需求，所以需要
//我们⾃⼰实现深拷⻉(对指向的资源也进⾏拷⻉)。像MyQueue这样的类型内部主要是⾃定义类型
//Stack成员，编译器⾃动⽣成的拷⻉构造会调⽤Stack的拷⻉构造，也不需要我们显⽰实现
//MyQueue的拷⻉构造。这⾥还有⼀个⼩技巧，如果⼀个类显⽰实现了析构并释放资源，那么他就
//需要显⽰写拷⻉构造，否则就不需要。

//6. 传值返回会产⽣⼀个临时对象调⽤拷⻉构造，传值引⽤返回，返回的是返回对象的别名(引⽤)，没
//有产⽣拷⻉。但是如果返回对象是⼀个当前函数局部域的局部对象，函数结束就销毁了，那么使⽤
//引⽤返回是有问题的，这时的引⽤相当于⼀个野引⽤，类似⼀个野指针⼀样。传引⽤返回可以减少
//拷⻉，但是⼀定要确保返回对象，在当前函数结束后还在，才能⽤引⽤返回。

#include<iostream>
using namespace std;
class Date
{
public:
	
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//拷贝构造函数
	//每次要调用拷贝构造函数之前要先传值传参，传值传参是一种拷贝又形成一个新的拷贝构造，就形成了无穷递归
	//所以要引用
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};



void Func1(Date& d)
{
	cout << &d << endl;
	d.Print();
}

typedef int STDataType;
class Stack
{
public:
	Stack(int n = 4)
	{
		_a = (STDataType*)malloc(sizeof(STDataType) * n);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = n;
		_top = 0;
	}

	//拷贝函数
	Stack(const Stack& st)
	{
		_a = (STDataType*)malloc(sizeof(STDataType) * st._capacity);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		}
		memcpy(_a, st._a, sizeof(STDataType) * st._top);
		_top = st._top;
		_capacity = st._capacity;
	}

	void Push(STDataType x)
	{
		if (_top == _capacity)
		{
			int newcapacity = _capacity * 2;
			STDataType* tmp = (STDataType*)realloc(_a ,sizeof(STDataType) * newcapacity);
			if (nullptr == tmp)
			{
				perror("malloc申请失败");
				return;
			}
			_a = tmp;
			_capacity = newcapacity;
		}
		_a[_top++] = x;
	}

	//析构函数
	~Stack()
	{
		//cout << "~Stack()" << endl;
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	STDataType* _a;
	size_t _capacity;
	size_t _top;
};


class MyQueue
{
public:
private:
	Stack pushst;
	Stack popst;
};


int main()
{
	Date d1(2024, 7, 10);
	Date d2(2024, 7, 12);
	d1.Print();

	//复值重载拷贝
	d1 = d2;

	//拷贝构造
	Date d3(d1);
	Date d4 = d1;
	d3.Print();
	d4.Print();

	//C++规定，传值传参要拷贝构造
	Func1(d1);

	return 0;
}



//5.1 运算符重载
//1. 当运算符被⽤于类类型的对象时，C++语⾔允许我们通过运算符重载的形式指定新的含义。C++规
//定类类型对象使⽤运算符时，必须转换成调⽤对应运算符重载，若没有对应的运算符重载，则会编
//译报错。

//2. 运算符重载是具有特殊名字的函数，他的名字是由operator和后⾯要定义的运算符共同构成。和其他
//函数⼀样，它也具有其返回类型和参数列表以及函数体。

//3. 重载运算符函数的参数个数和该运算符作⽤的运算对象数量⼀样多。⼀元运算符有⼀个参数，⼆元
//运算符有两个参数，⼆元运算符的左侧运算对象传给第⼀个参数，右侧运算对象传给第⼆个参数。

//4. 如果⼀个重载运算符函数是成员函数，则它的第⼀个运算对象默认传给隐式的this指针，因此运算
//符重载作为成员函数时，参数⽐运算对象少⼀个。

//5. 运算符重载以后，其优先级和结合性与对应的内置类型运算符保持⼀致。

//6. 不能通过连接语法中没有的符号来创建新的操作符：⽐如operator@。

//7. .* :: sizeof ? : .注意以上5个运算符不能重载。(选择题⾥⾯常考，⼤家要记⼀
//	下)

//8. 重载操作符⾄少有⼀个类类型参数，不能通过运算符重载改变内置类型对象的含义，如： int
//	operator+(int x, int y)

//9. ⼀个类需要重载哪些运算符，是看哪些运算符重载后有意义，⽐如Date类重载operator - 就有意
//	义，但是重载operator + 就没有意义。

//10. 重载++运算符时，有前置++和后置++，运算符重载函数名都是operator++，⽆法很好的区分。
//C++规定，后置++重载时，增加⼀个int形参，跟前置++构成函数重载，⽅便区分。

//11. 重载 << 和 >> 时，需要重载为全局函数，因为重载为成员函数，this指针默认抢占了第⼀个形参位
//置，第⼀个形参位置是左侧运算对象，调⽤时就变成了 对象 << cout，不符合使⽤习惯和可读性。
//重载为全局函数把ostream / istream放到第⼀个形参位置就可以了，第⼆个形参位置当类类型对
//象。 

class A
{
public:
	void func()
	{
		cout << "A::func()" << endl;
	}
};
typedef void(A::* PF)(); //成员函数指针类型

int main1()
{
	//void(A:: * pf)() = nullptr;
	PF pf = nullptr;

	//C++规定成员函数要加&才能取到函数指针
	pf = &A::func;  

	return 0;
}